﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Common;
using System.Data.EntityClient;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using AzureDiagMon.Common;
using AzureDiagMon.Common.Interfaces;
using AzureDiagMon.DatabaseLayer.Common.Properties;
using AzureDiagMon.Model;
using AzureDiagMon.Model.Helpers;
using AzureDiagMon.Common.Helpers;

namespace AzureDiagMon.DatabaseLayer.Common
{
    public abstract class DatabaseLayer : IDatabaseLayer
    {
        protected readonly static EntityConnectionStringBuilder EntityConnectionStringBuilder;
        protected readonly static DbConnectionStringBuilder ProviderConnectionStringBuilder;
        protected readonly static string CustomSSDLFileName = Path.Combine(PathHelper.AppDataFolder, "Models.ssdl");

        #region Implementation of IDatabaseLayer

        protected abstract void InitInternal();

        #endregion

        public void Init()
        {
            InitInternal();
            CreateSampleData();
        }

        static DatabaseLayer()
        {
            EntityConnectionStringBuilder = new EntityConnectionStringBuilder(
                ConfigurationManager.ConnectionStrings["ModelsContainer"].ConnectionString);
            ProviderConnectionStringBuilder = new DbConnectionStringBuilder { ConnectionString = EntityConnectionStringBuilder.ProviderConnectionString };
        }

        protected DatabaseLayer()
        {
            RegisterContextCreator();
        }

        private void CreateSampleData()
        {
            if (Settings.Default.CreateSampleData)
            {
                Debug.WriteLine("Creating sample data...");
                SampleDataHelper.Init();
                Debug.WriteLine("Sample data created.");
            }
        }

        private void RegisterContextCreator()
        {
            var contextProvider = IoC.Get<DataContextProvider>();
            contextProvider.SetContextCreator(
                () =>
                {
                    var con = new EntityConnection(EntityConnectionStringBuilder.ConnectionString);
                    var ctx = new ModelsContainer(con);

                    //TODO: Find out how to check the connection
                    ctx.SubscriptionSet.FirstOrDefault();
                    return ctx;
                });
        }

        protected virtual string GetDatabaseFileName()
        {
            return PathHelper.AppName;
        }

        protected string GetDatabasePath()
        {
            return Path.Combine(PathHelper.AppDataFolder, GetDatabaseFileName());
        }

        protected IEnumerable<string> GetDatabaseCreationScriptStatements()
        {
            var assembly = typeof(ModelsContainer).Assembly;
            var stream = assembly.GetManifestResourceStream("AzureDiagMon.Model.Models.edmx.sqlce");
            if (stream != null)
            {
                var script = new StreamReader(stream).ReadToEnd();
                stream = assembly.GetManifestResourceStream("AzureDiagMon.Model.Models.post.sqlce");
                if (stream != null)
                    script = String.Concat(script, new StreamReader(stream).ReadToEnd());

                var result = script.Split(new[] { "\r\nGO\r\n" }, StringSplitOptions.None);

                //The generated script has some remarked text after the last GO statement
                return result.Take(result.Length - 1)
                             .Select(StripSqlStatement);
            }
            throw new Exception("Script not found!");

        }

        protected string StripSqlStatement(string statement)
        {
            using (var reader = new StringReader(statement))
            {
                var sb = new StringBuilder();
                while (true)
                {
                    var line = reader.ReadLine();
                    if (line == null)
                        break;

                    if (line.StartsWith("--") || String.IsNullOrEmpty(line))
                        continue;

                    sb.AppendLine(line);
                }

                return sb.ToString();
            }
        }

        public const string SQLCE = "SQLCE";
        public const string SQLServer = "SQLServer";

        public static IDatabaseLayer SelectDatabaseLayerByConnectionString()
        {
            IDatabaseLayer result;
            switch (EntityConnectionStringBuilder.Provider)
            {
                case "System.Data.SqlServerCe.4.0":
                    result = IoC.Get<IDatabaseLayer>(SQLCE);
                    break;
                case "System.Data.SqlClient":
                    result = IoC.Get<IDatabaseLayer>(SQLServer);
                    break;
                default:
                    throw new InvalidDataProviderException(String.Format("Invalid data provider: {0}", EntityConnectionStringBuilder.Provider));
            }

            Trace.WriteLine(String.Format(Resources.DatabaseLayer_SelectDatabaseLayerByConnectionString_Database_engine_selected_by_connection_string, result.GetType().Name));
            return result;
        }

        #region SSDL manipulation

        protected XDocument GetSSDL()
        {
            var a = typeof(ModelsContainer).Assembly;
            using (var s = a.GetManifestResourceStream("Models.ssdl"))
            {
                return XDocument.Load(s);
            }
        }

        protected void SetSSDL(XDocument doc)
        {
            doc.Save(CustomSSDLFileName);
            EntityConnectionStringBuilder.Metadata =
                String.Format("res://*/Models.csdl|{0}|res://*/Models.msl", CustomSSDLFileName);
        }

        protected void ReplaceProviderInSSDL(string provider, string providerVersion)
        {
            if (provider == null) throw new ArgumentNullException("provider");
            if (providerVersion == null) throw new ArgumentNullException("providerVersion");

            var doc = GetSSDL();
            var schemaNode = doc.Root;
            schemaNode.Attribute("Provider").Value = provider;
            schemaNode.Attribute("ProviderManifestToken").Value = providerVersion;
            SetSSDL(doc);
        }

        #endregion

    }
}
